﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using FakeItEasy;
using FakeItEasy.ExtensionSyntax.Full;
using Legend.Collections;
using Legend.Tests;
using Legend.Threading;
using NUnit.Framework;

namespace Legend.Core.Tests.Collections.SynchronizedDictionaryTests
{
    [TestFixture]
    public class SynchronizedDictionaryTests
    {
        private IDictionary<string, string> wrappedDictionary;
        private ISynchronizationManager synchronizationManager;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            this.wrappedDictionary = new Dictionary<string, string>();
            this.synchronizationManager = A.Fake<ISynchronizationManager>();
            A.CallTo(() =>  synchronizationManager.RecursionPolicy)
                .Returns(LockRecursionPolicy.SupportsRecursion);
        }

        private SynchronizedDictionary<string, string> CreateDictionary()
        {
            return new SynchronizedDictionary<string, string>(this.wrappedDictionary, this.synchronizationManager);
        }

        [Test]
        public void GetValue_should_be_null_guarded()
        {
            var dictionary = this.CreateDictionary();
            
            NullGuardedConstraint.Assert(() => dictionary.GetValue("foo", () => "bar"));
        }

        [Test]
        public void GetValue_should_return_value_from_dictionary_when_it_exists()
        {
            // Arrange
            this.wrappedDictionary.Add("foo", "bar");

            var dictionary = this.CreateDictionary();

            // Act
            var result = dictionary.GetValue("foo", () => "baz");

            // Assert
            Assert.That(result, Is.EqualTo("bar"));
        }

        [Test]
        public void GetValue_should_return_value_produced_by_value_producer_when_key_does_not_exist_in_dictionary()
        {
            // Arrange
            var dictionary = this.CreateDictionary();

            // Act
            var result = dictionary.GetValue("foo", () => "produced");

            // Assert
            Assert.That(result, Is.EqualTo("produced"));
        }

        [Test]
        public void GetValue_should_insert_value_produced_by_value_producer_when_key_does_not_exist_in_dictionary()
        {
            // Arrange
            var dictionary = this.CreateDictionary();

            // Act
            var result = dictionary.GetValue("foo", () => "produced");

            // Assert
            Assert.That(this.wrappedDictionary["foo"], Is.EqualTo("produced"));
        }

        [Test]
        public void GetValue_should_acquire_upgradable_lock_when_called()
        {
            // Arrange
            this.wrappedDictionary.Add("foo", "bar");

            var dictionary = this.CreateDictionary();

            // Act
            var result = dictionary.GetValue("foo", () => "produced");

            // Assert
            A.CallTo(() => this.synchronizationManager.EnterUpgradableReadLock()).MustHaveHappened();
            A.CallTo(() => this.synchronizationManager.ExitUpgradableReadLock()).MustHaveHappened();
        }

        [Test]
        public void GetValue_should_acquire_write_lock_when_key_does_not_exist_in_dictionary()
        {
            // Arrange
            var dictionary = this.CreateDictionary();

            // Act
            var result = dictionary.GetValue("foo", () => "produced");

            // Assert
            A.CallTo(() => this.synchronizationManager.EnterWriteLock()).MustHaveHappened();
            A.CallTo(() => this.synchronizationManager.ExitWriteLock()).MustHaveHappened();
        }
    }

    public class SynchronizedDictionaryTestCase
    {
        protected SynchronizedDictionary<string, string> dictionary;
        protected IDictionary<string, string> wrappedDictionary;
        protected ISynchronizationManager readerWriterLock;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            readerWriterLock = A.Fake<ISynchronizationManager>();
            A.CallTo(() => readerWriterLock.RecursionPolicy).Returns(System.Threading.LockRecursionPolicy.SupportsRecursion);

            wrappedDictionary = CreateWrappedDictionary();
            dictionary = new SynchronizedDictionary<string, string>(wrappedDictionary, readerWriterLock);
        }

        protected virtual IDictionary<string, string> CreateWrappedDictionary()
        {
            return A.Fake<IDictionary<string, string>>();
        }
    }

    [TestFixture]
    public class Constructor
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_collection_is_null()
        {
            var c = new SynchronizedDictionary<string, string>(null, A.Fake<ISynchronizationManager>());
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_readerWriterLock_is_null()
        {
            var c = new SynchronizedDictionary<string, string>(new Dictionary<string, string>(), null);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void should_throw_exception_when_readerWriterLock_does_not_allow_recursion()
        {
            var readerWriterLock = A.Fake<ISynchronizationManager>();
            A.CallTo(() => readerWriterLock.RecursionPolicy).Returns(System.Threading.LockRecursionPolicy.NoRecursion);
            
            var c = new SynchronizedDictionary<string, string>(
                new Dictionary<string, string>(),
                readerWriterLock);
        }
    }

    [TestFixture]
    public class dictionary_Add
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            this.dictionary.Add("foo", "bar");
            
            A.CallTo(() => this.wrappedDictionary.Add("foo", "bar")).MustHaveHappened();
        }

        [Test]
        public void should_acquire_writer_lock()
        {
            this.dictionary.Add("foo", "bar");

            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.dictionary.Add("foo", "bar");
            
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception_in_add()
        {
            A.CallTo(() => this.wrappedDictionary.Add("foo", "bar")).Throws(new ArgumentNullException());

            try
            {
                this.dictionary.Add("foo", "bar");
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class collection_Add
        : SynchronizedDictionaryTestCase
    {
        KeyValuePair<string, string> value;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            value = new KeyValuePair<string, string>("foo", "bar");
        }

        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            this.dictionary.Add(value);
           
            A.CallTo(() => this.wrappedDictionary.Add(value)).MustHaveHappened();
        }

        [Test]
        public void should_acquire_writer_lock()
        {
            this.dictionary.Add(value);
            
            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.dictionary.Add(value);
            
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception_in_add()
        {
            A.CallTo(() => this.wrappedDictionary.Add(value)).Throws(new ArgumentNullException());

            try
            {
                this.dictionary.Add(value);
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class Clear
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            this.dictionary.Clear();
            
            A.CallTo(() => wrappedDictionary.Clear()).MustHaveHappened();
        }

        [Test]
        public void should_acquire_write_lock()
        {
            dictionary.Clear();
            
            A.CallTo(() => readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_write_lock()
        {
            dictionary.Clear();
            
            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_write_lock_when_exception_is_thrown_in_wrappedCollection()
        {
            A.CallTo(() => this.wrappedDictionary.Clear()).Throws(new NotSupportedException());

            try
            {
                dictionary.Clear();
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class collection_Contains
        : SynchronizedDictionaryTestCase
    {
        KeyValuePair<string, string> value;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            value = new KeyValuePair<string, string>("foo", "bar");
        }

        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            A.CallTo(() => this.wrappedDictionary.Contains(value)).Returns(false);

            var returned = dictionary.Contains(value);
            Assert.IsFalse(returned);
        }

        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            A.CallTo(() => this.wrappedDictionary.Contains(value)).Returns(true);

            var returned = dictionary.Contains(value);
            Assert.IsTrue(returned);
        }

        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            dictionary.Contains(value);
            A.CallTo(() => wrappedDictionary.Contains(value)).MustHaveHappened();
        }

        [Test]
        public void should_acquire_read_lock()
        {
            dictionary.Contains(value);
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock()
        {
            dictionary.Contains(value);
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_wrapped_collection_throws_exception()
        {
            wrappedDictionary
                .CallsTo(x => x.Contains(value))
                .Throws(new NotSupportedException());

            try
            {
                dictionary.Contains(value);
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class CopyTo
        : SynchronizedDictionaryTestCase 
    {
        KeyValuePair<string, string>[] value = new KeyValuePair<string, string>[10];

        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            dictionary.CopyTo(value, 0);
            A.CallTo(() => wrappedDictionary.CopyTo(value, 0)).MustHaveHappened();
        }

        [Test]
        public void should_acquire_read_lock()
        {
            dictionary.CopyTo(value, 0);
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock()
        {
            dictionary.CopyTo(value, 0);
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_wrapped_collection_throws_exception()
        {
            wrappedDictionary
                .CallsTo(x => x.CopyTo(value, 0))
                .Throws(new NotSupportedException());

            try
            {
                dictionary.CopyTo(value, 0);
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class Count
        : SynchronizedDictionaryTestCase
    {

        [Test]
        public void should_delegate_call_to_wrapped_collection()
        {
            var count = dictionary.Count;
            
            A.CallTo(() => wrappedDictionary.Count).MustHaveHappened(Repeated.Exactly.Once);
        }

        [Test]
        public void should_acquire_read_lock()
        {
            var count = dictionary.Count;
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock()
        {
            var count = dictionary.Count;
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_wrapped_collection_throws_exception()
        {
            wrappedDictionary
                .CallsTo(x => x.Count)
                .Throws(new NotSupportedException());

            try
            {
                var count = dictionary.Count;
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class IsReadOnly
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            wrappedDictionary.CallsTo(x => x.IsReadOnly).Returns(true);
            Assert.IsTrue(dictionary.IsReadOnly);
        }

        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            wrappedDictionary.CallsTo(x => x.IsReadOnly).Returns(false);
            Assert.IsFalse(dictionary.IsReadOnly);
        }
    }

    [TestFixture]
    public class collection_Remove
        : SynchronizedDictionaryTestCase
    {
        KeyValuePair<string, string> value;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            value = new KeyValuePair<string, string>("foo", "bar");
        }

        [Test]
        public void should_call_remove_on_wrapped_collection()
        {
            dictionary.Remove(value);
            A.CallTo(() => wrappedDictionary.Remove(value)).MustHaveHappened();
        }

        [Test]
        public void should_return_true_when_wrapped_collection_returns_true()
        {
            wrappedDictionary.CallsTo(x => x.Remove(value)).Returns(true);
            Assert.IsTrue(dictionary.Remove(value));
        }

        [Test]
        public void should_return_false_when_wrapped_collection_returns_false()
        {
            wrappedDictionary.CallsTo(x => x.Remove(value)).Returns(false);
            Assert.IsFalse(dictionary.Remove(value));
        }

        [Test]
        public void should_acquire_writer_lock()
        {
            this.dictionary.Remove(value);
            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.dictionary.Remove(value);
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception()
        {
            this.wrappedDictionary
                .CallsTo(x => x.Remove(value))
                .Throws(new ArgumentNullException());

            try
            {
                this.dictionary.Remove(value);
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class GetEnumerator
        : SynchronizedDictionaryTestCase
    {
        IEnumerator<KeyValuePair<string, string>> enumerator;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            enumerator = A.Fake<IEnumerator<KeyValuePair<string, string>>>();
            wrappedDictionary.CallsTo(x => x.GetEnumerator()).Returns(enumerator);
        }

        [Test]
        public void should_call_get_enumerator_on_wrapped_collection()
        {
            dictionary.GetEnumerator();
            A.CallTo(() => wrappedDictionary.GetEnumerator()).MustHaveHappened();
        }

        [Test]
        public void should_return_a_locking_enumerator()
        {
            var e = dictionary.GetEnumerator();
            Assert.AreEqual(typeof(LockingEnumerator<KeyValuePair<string, string>>), e.GetType());
        }
    }

    [TestFixture]
    public class non_generic_GetEnumerator
        : SynchronizedDictionaryTestCase
    {
        IEnumerator<KeyValuePair<string, string>> enumerator;

        protected override void OnSetUp()
        {
            base.OnSetUp();
            enumerator = A.Fake<IEnumerator<KeyValuePair<string, string>>>();
            wrappedDictionary.CallsTo(x => x.GetEnumerator()).Returns(enumerator);
        }

        [Test]
        public void should_call_get_enumerator_on_wrapped_collection()
        {
            ((IEnumerable)dictionary).GetEnumerator();
            A.CallTo(() => wrappedDictionary.GetEnumerator()).MustHaveHappened();
        }

        [Test]
        public void should_return_a_locking_enumerator()
        {
            var e = ((IEnumerable)dictionary).GetEnumerator();
            Assert.AreEqual(typeof(LockingEnumerator<KeyValuePair<string, string>>), e.GetType());
        }
    }

    [TestFixture]
    public class AcquireReadLock
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_acquire_read_lock()
        {
            using (dictionary.AcquireReadLock()) { }
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_disposed()
        {
            using (dictionary.AcquireReadLock()) { }
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class AcquireWriteLock
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_acquire_read_lock()
        {
            using (dictionary.AcquireWriteLock()) { }
            A.CallTo(() => readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_disposed()
        {
            using (dictionary.AcquireWriteLock()) { }
            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class ContainsKey
        : SynchronizedDictionaryTestCase 
    {
        [Test]
        public void should_delegate_call_to_wrapped_dictionary()
        {
            dictionary.ContainsKey("foo");
            A.CallTo(() => wrappedDictionary.ContainsKey("foo")).MustHaveHappened();
        }

        [Test]
        public void should_return_true_when_wrapped_dictionary_returns_true()
        {
            wrappedDictionary.CallsTo(x => x.ContainsKey("foo")).Returns(true);

            var returned = dictionary.ContainsKey("foo");

            Assert.IsTrue(returned);
        }

        [Test]
        public void should_return_false_when_wrapped_dictionary_returns_false()
        {
            wrappedDictionary.CallsTo(x => x.ContainsKey("foo")).Returns(false);

            var returned = dictionary.ContainsKey("foo");

            Assert.IsFalse(returned);
        }

        [Test]
        public void should_acquire_lock()
        {
            this.dictionary.ContainsKey("foo");
            A.CallTo(() => this.readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.dictionary.ContainsKey("foo");
            A.CallTo(() => this.readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception()
        {
            this.wrappedDictionary
                .CallsTo(x => x.ContainsKey("foo"))
                .Throws(new ArgumentNullException());

            try
            {
                this.dictionary.ContainsKey("foo");
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class Keys
        : SynchronizedDictionaryTestCase 
    {
        [Test]
        public void should_return_SynchronizedCollection()
        {
            var keys = dictionary.Keys;
            Assert.AreEqual(typeof(SynchronizedCollection<string>), keys.GetType());
        }

        [Test]
        public void should_return_same_sequence_of_keys_as_wrapped_dictionary()
        {
            var keys = dictionary.Keys;
            Assert.IsTrue(keys.SequenceEqual(wrappedDictionary.Keys));
        }

        [Test]
        public void should_acquire_read_lock_when_getting_enumerator()
        {
            dictionary.Keys.GetEnumerator();
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_disposing_enumerator()
        {
            var e = dictionary.Keys.GetEnumerator();
            e.Dispose();

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>() {
                {"foo", "bar" },
                {"lorem", "ipsum" }
            };
        }
    }

    [TestFixture]
    public class Values
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_return_SynchronizedCollection()
        {
            var values = dictionary.Values;
            Assert.AreEqual(typeof(SynchronizedCollection<string>), values.GetType());
        }

        [Test]
        public void should_return_same_sequence_of_values_as_wrapped_dictionary()
        {
            var values = dictionary.Values;
            Assert.IsTrue(values.SequenceEqual(wrappedDictionary.Values));
        }

        [Test]
        public void should_acquire_read_lock_when_getting_enumerator()
        {
            dictionary.Values.GetEnumerator();
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_disposing_enumerator()
        {
            var e = dictionary.Values.GetEnumerator();
            e.Dispose();

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>() {
                {"foo", "bar" },
                {"lorem", "ipsum" }
            };
        }
    }

    [TestFixture]
    public class dictionary_Remove
        : SynchronizedDictionaryTestCase
    {
        [Test]
        public void should_delegate_call_to_wrapped_dictionary()
        {
            dictionary.Remove("foo");
            A.CallTo(() => wrappedDictionary.Remove("foo")).MustHaveHappened();
        }

        [Test]
        public void should_return_true_when_wrapped_dictionary_returns_true()
        {
            wrappedDictionary.CallsTo(x => x.Remove("foo")).Returns(true);
            var returned = dictionary.Remove("foo");

            Assert.IsTrue(returned);
        }

        [Test]
        public void should_return_false_when_wrapped_dictionary_returns_false()
        {
            wrappedDictionary.CallsTo(x => x.Remove("foo")).Returns(false);
            var returned = dictionary.Remove("foo");

            Assert.IsFalse(returned);
        }

        [Test]
        public void should_acquire_lock()
        {
            this.dictionary.Remove("foo");
            A.CallTo(() => this.readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_lock()
        {
            this.dictionary.Remove("foo");
            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_lock_when_wrapped_collection_throws_exception()
        {
            this.wrappedDictionary
                .CallsTo(x => x.Remove("foo"))
                .Throws(new ArgumentNullException());

            try
            {
                this.dictionary.Remove("foo");
            }
            catch (ArgumentNullException) { }

            A.CallTo(() => this.readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class getting_from_indexer
        : SynchronizedDictionaryTestCase 
    {
        [Test]
        public void should_delegate_call_to_wrapped_dictionary()
        {
            A.CallTo(() => wrappedDictionary["foo"]).Returns("bar");
            var o = dictionary["foo"];

            Assert.That(o, Is.EqualTo("bar"));
        }

        [Test]
        public void should_return_value_returned_from_wrapped_dictionary()
        {
            wrappedDictionary.CallsTo(x => x["foo"]).Returns("bar");
            var returned = dictionary["foo"];
            Assert.AreEqual("bar", returned);
        }

        [Test]
        public void should_acquire_read_lock()
        {
            var o = dictionary["foo"];
            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_read_lock()
        {
            var o = dictionary["foo"];
            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_release_read_lock_when_wrapped_dictionary_throws_exception()
        {
            wrappedDictionary
                .CallsTo(x => x["foo"])
                .Throws(new NotSupportedException())
                .Once();

            try
            {
                var o = dictionary["foo"];
            }
            catch (NotSupportedException) { }

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }

    [TestFixture]
    public class setting_to_indexer
        : SynchronizedDictionaryTestCase 
    {
        [Test]
        public void should_acquire_write_lock()
        {
            dictionary["foo"] = "bar";
            A.CallTo(() => readerWriterLock.EnterWriteLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_write_lock()
        {
            dictionary["foo"] = "bar";
            A.CallTo(() => readerWriterLock.ExitWriteLock()).MustHaveHappened();
        }

        [Test, Ignore]
        public void should_release_write_lock_when_wrapped_dictionary_throws_exception()
        {
            //wrappedDictionary
            //    .CallsTo(x => x["foo"] = "bar")
            //    .Throws(new NotSupportedException()).Once();

            //try
            //{
            //    dictionary["foo"] = "bar";
            //}
            //catch (NotSupportedException) { }

            //readerWriterLock.Assert().WasCalled(x => x.ExitWriteLock());
        }
    }

    [TestFixture]
    public class TryGetValue
        : SynchronizedDictionaryTestCase 
    {
        [Test]
        public void should_return_true_when_wrapped_dictionary_returns_true()
        {
            string value = null;
            var returned = dictionary.TryGetValue("foo", out value);
            Assert.IsTrue(returned);
        }

        [Test]
        public void should_return_false_when_wrapped_dictionary_returns_false()
        {
            string value = null;
            var returned = dictionary.TryGetValue("nokey", out value);
            Assert.IsFalse(returned);
        }

        [Test]
        public void sets_existing_value_to_output_parameter()
        {
            string value = null;
            dictionary.TryGetValue("foo", out value);
            Assert.AreEqual("bar", value);
        }

        protected override IDictionary<string, string> CreateWrappedDictionary()
        {
            return new Dictionary<string, string>() {
                { "foo", "bar" }
            };
        }

        [Test]
        public void should_acquire_read_lock()
        {
            string value = null;
            dictionary.TryGetValue("foo", out value);

            A.CallTo(() => readerWriterLock.EnterReadLock()).MustHaveHappened();
        }

        [Test]
        public void should_realease_write_lock()
        {
            string value = null;
            dictionary.TryGetValue("foo", out value);

            A.CallTo(() => readerWriterLock.ExitReadLock()).MustHaveHappened();
        }
    }
}